#ifndef __ITERATOR_CONTROL__
#define __ITERATOR_CONTROL__

#include <euserhl.h>
#include <aknlists.h>
#include <aknsfld.h>
#include <eiklbo.h>
#include <eikapp.h>
#include "Iterator.h"
#include <AknsBasicBackgroundControlContext.h>
#include <aknviewappui.h>
#include "FunTrace.h"

#define EAknEditorFlagEnablePartialScreen             0x200000
#define EAknEditorFlagLaunchPenInputAutomatic         0x400000


#include <aknglobalnote.h>
static void PrintNote2L(TRefByValue<const TDesC> aFormat, ...)
    {
    VA_LIST list;
    VA_START(list, aFormat);
    TBuf<256> buf;
    buf.FormatList(aFormat, list);
    VA_END(list);
	CAknGlobalNote* globalNote = CAknGlobalNote::NewLC();
	globalNote->ShowNoteL(EAknGlobalInformationNote, buf); 
    CleanupStack::PopAndDestroy(globalNote);
    }


template<class T, TInt (T::*F)()>
TInt MemFunDelegate(TAny* aPtr)
	{
	T* obj = static_cast<T*>(aPtr);
	return (*obj.*F)();
	}

struct CIteratorControlState
	{
	TInt iTopItemIndex;
	TInt iCurrentItemIndex;
	TBool iSearchFieldIsVisible;
	LManagedPtr<HBufC> iSearchText;
	CIteratorControlState() : iTopItemIndex(-1), iCurrentItemIndex(-1), iSearchFieldIsVisible(EFalse) { }
	};


class CBaseIteratorControl : public CCoeControl, public MListBoxModel, public MDesC16Array, public MCoeControlObserver, public MEikCommandObserver, public MEikListBoxObserver, public MCoeViewDeactivationObserver
	{
public:
	//LManagedPtr<CAknDoubleStyleListBox> iListBox;
	//LManagedPtr<CEikFormattedCellListBoxTypedef> iListBox;
	LManagedPtr<CEikListBox> iListBox;
	LManagedPtr<CAknSearchField> iSearchField;
	TInt iLastScanCode;
	RBuf iSearchText;
    LManagedRef<RBuf> iManagedSearchText;
    mutable RBuf iArrayItem;
    LManagedRef<RBuf> iManagedArrayItem;
    LManagedPtr< scalp::CFunction<void(const TDesC& aDes, MCoeControlObserver::TCoeEvent)> > iSearchTextChangedFun;
    LManagedPtr< scalp::CFunction<void(CEikListBox* aListBox, TListBoxEvent aEventType)> > iListBoxObserverFun;
    LManagedPtr<CAsyncCallBack> iShowScrollBarCallback;
    LManagedPtr<CAsyncCallBack> iOfferKeyEventLCallback;
    TInt iViewId;
    
    virtual void HandleListBoxEventL(CEikListBox* aListBox, TListBoxEvent aEventType)
    	{
    	if(iListBoxObserverFun.Get())
    		(*iListBoxObserverFun)(aListBox, aEventType);
    	}
    
    CBaseIteratorControl() : iManagedSearchText(iSearchText), iManagedArrayItem(iArrayItem)
    	{
    	}
    ~CBaseIteratorControl()
    	{
    	iArrayItem.Close();
    	iSearchText.Close();
    	}

    CIteratorControlState* GetStateLC()
    	{
    	CIteratorControlState* state = new (ELeave) CIteratorControlState;
    	CleanupStack::PushL(state);
    	state->iCurrentItemIndex = iListBox->CurrentItemIndex();
    	state->iTopItemIndex = iListBox->TopItemIndex();
    	state->iSearchFieldIsVisible = iSearchField->IsVisible();
    	if(iSearchField->IsVisible())
    		{
    		iSearchField->GetSearchText(iSearchText);
    		state->iSearchText = iSearchText.AllocL();
    		}
    	return state;
    	}
    
    void StoreStateL(CIteratorControlState* aState)
    	{
    	aState->iCurrentItemIndex = iListBox->CurrentItemIndex();
    	aState->iTopItemIndex = iListBox->TopItemIndex();
    	aState->iSearchFieldIsVisible = iSearchField->IsVisible();
    	if(iSearchField->IsVisible())
    		{
    		iSearchField->GetSearchText(iSearchText);
    		aState->iSearchText = iSearchText.AllocL();
    		}
    	}
    
    void RestoreStateL(const CIteratorControlState* aState)
    	{
    	if(aState->iTopItemIndex != -1)
    		iListBox->SetTopItemIndex(aState->iTopItemIndex);
    	if(aState->iCurrentItemIndex != -1)
    		iListBox->SetCurrentItemIndex(aState->iCurrentItemIndex);
    	if(aState->iSearchFieldIsVisible)
    		{
    		iSearchText.Copy(*aState->iSearchText);
    		iSearchField->SetSearchTextL(*aState->iSearchText);
    		ShowSearchFieldL();
        	(*iSearchTextChangedFun)(iSearchText, MCoeControlObserver::EEventStateChanged);
        	iListBox->HandleItemRemovalL();
        	iListBox->HandleItemAdditionL();
    		}
    	}

    void HandleViewDeactivation(const TVwsViewId& aViewIdToBeDeactivated,const TVwsViewId& aNewlyActivatedViewId)
    	{
    	if(aNewlyActivatedViewId.iAppUid == CEikonEnv::Static()->EikAppUi()->Application()->AppDllUid() && iSearchField.Get() && iSearchField->IsFocused() && iSearchField->IsVisible())
    		{
    		iSearchField->SetFocus(EFalse);
    		}
    	}


    void HandleControlEventL(CCoeControl* aControl,MCoeControlObserver::TCoeEvent aEventType)
    	{
    	LCleanedupPtr<HBufC> prevSearchText ( iSearchText.AllocL() );
    	iSearchField->GetSearchText(iSearchText);
    	
    	if(aEventType == MCoeControlObserver::EEventStateChanged && *prevSearchText == iSearchText) return;

    	(*iSearchTextChangedFun)(iSearchText, aEventType);

    	
    	iListBox->HandleItemRemovalL();
    	iListBox->HandleItemAdditionL();
    	}

    void ConstructL(const TRect& aRect, CEikListBox* aListBox, CCoeControl* aParent = NULL, TInt aViewId = KErrNone)
    	{
    	LCleanedupPtr<CEikListBox> listBox(aListBox);
    	iArrayItem.CreateL(1024);
    	
    	if(aParent)
    		SetContainerWindowL(*aParent);
    	else 
    		CreateWindowL();

    	iViewId = aViewId;

    	iListBox = listBox.Unmanage();
    	iListBox->SetContainerWindowL(*this);
    	iListBox->SetListBoxObserver( this );
    	iListBox->CreateScrollBarFrameL(ETrue);
    	//set scroll bar visibility, has to be before SetRect! http://wiki.forum.nokia.com/index.php/UI_Designer_Listbox_Scrollbar
    	iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOn, CEikScrollBarFrame::EOn);	
		iSearchField = CAknSearchField::NewL( *this, CAknSearchField::EPopup, NULL, 30);
		iSearchField->MakeVisible(EFalse);
		iSearchText.CreateL(30);
		iSearchField->SetObserver(this);
		iSearchField->Editor().SetAknEditorFlags( iSearchField->Editor().AknEdwinFlags() | EAknEditorFlagEnablePartialScreen | EAknEditorFlagLaunchPenInputAutomatic | EAknEditorFlagDefault );
    	iLastScanCode = KErrNotFound;
    	iShowScrollBarCallback = new (ELeave) CAsyncCallBack( TCallBack(&MemFunDelegate<CBaseIteratorControl, &CBaseIteratorControl::ShowScrollBarCallback>, this), CActive::EPriorityStandard);
    	iAvkonViewAppUi->AddViewDeactivationObserverL(this);
    	SetRect(aRect);
    	ActivateL();
    	}

    TInt ShowScrollBarCallback()
    	{
    	return 0;
    	}
    
    
	virtual void DrawActualItemPublic(TInt aIndex, const TRect& aActualItemRect,
			TBool aItemIsCurrent, TBool aViewIsEmphasized, TBool aViewIsDimmed, 
			TBool aItemIsSelected) const
			{
			
			}
    
    void SizeChanged()
    	{

		AknFind::HandlePopupFindSizeChanged(this, iListBox.Get(), iSearchField.Get());
    		
		if(iSearchField->IsVisible() && ( iSearchField->PositionRelativeToScreen().iY > PositionRelativeToScreen().iY + Rect().Height() ) ) // split screen
			{
			iSearchField->SetPosition(
					TPoint(iSearchField->Position().iX, PositionRelativeToScreen().iY + Rect().Height() - iSearchField->Rect().Height())
							);
			}
     	
     	return;

    	
    	if(0 == 0)
    		{
    		// this solution introduces flickering
    		iListBox->DrawBackground(Rect());
     	AknFind::HandlePopupFindSizeChanged(this, iListBox.Get(), iSearchField.Get());
    	iListBox->SetRect(
    			TRect(
    					iListBox->Rect().iTl.iX,
    					Rect().iTl.iY,
    					iListBox->Rect().iBr.iX,
    					iListBox->Rect().iBr.iY
    				)
    			);
    	iListBox->ScrollBarFrame()->VerticalScrollBar()->SetPosition( 
    			(TPoint(
    					iListBox->ScrollBarFrame()->VerticalScrollBar()->Position().iX,
    					iListBox->Position().iY
    					)
    			));
    	return;
    		}
    	
    	if(iSearchField->IsVisible())
    		{
    		TInt searchFieldHeight = iListBox->ItemHeight();
        	iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOff, CEikScrollBarFrame::EOff);	
        	iListBox->SetRect(
        			TRect(
						Rect().iTl.iX,
						Rect().iTl.iY,
						Rect().iBr.iX,
						Rect().iBr.iY - searchFieldHeight
						)
        			);
        	iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOn, CEikScrollBarFrame::EOn);        	
        	iListBox->ScrollBarFrame()->VerticalScrollBar()->SetRect( 
        			(TRect(
        					Rect().iBr.iX - iListBox->ScrollBarFrame()->VerticalScrollBar()->Rect().Width(),
        					Rect().iTl.iY,
        					Rect().iBr.iX,
        					Rect().iBr.iY - searchFieldHeight
        					)
        			));
        	iSearchField->SetRect(
        			TRect(
						Rect().iTl.iX,
						Rect().iBr.iY + Rect().iTl.iY - searchFieldHeight,
						Rect().iBr.iX,
						Rect().iBr.iY + Rect().iTl.iY
						)
        			);
    		}
    	else
    		{
        	iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOff, CEikScrollBarFrame::EOff);	
        	iListBox->SetRect(
        			Rect()
        			);
        	iListBox->ScrollBarFrame()->SetScrollBarVisibilityL(CEikScrollBarFrame::EOn, CEikScrollBarFrame::EOn);        	
        	iListBox->ScrollBarFrame()->VerticalScrollBar()->SetRect( 
        			(TRect(
        					Rect().iBr.iX - iListBox->ScrollBarFrame()->VerticalScrollBar()->Rect().Width(),
        					Rect().iTl.iY,
        					Rect().iBr.iX,
        					Rect().iBr.iY
        					)
        			));
        	iListBox->DrawDeferred();
    		}
    	}

    void HandlePointerEventL(const TPointerEvent& aPointerEvent)
    	{
    	CCoeControl::HandlePointerEventL(aPointerEvent);
    	}

    TInt CountComponentControls() const
    	{
    	TInt count = 0;
    	if(iListBox.Get()) ++count;
    	if(iSearchField.Get()) ++count;
    	return (count);
    	}

    CCoeControl* ComponentControl(TInt aIndex) const
    	{
    	switch (aIndex)
    		{
    		case 0:
    			return (iListBox.Get());
    		case 1:
    			return (iSearchField.Get());
    		default:
    			return (NULL);
    		}
    	}

    LManagedPtr< scalp::CFunction<void()> > iBreakpoint;

    void HideSearchFieldL()
    	{
    	if(iSearchField->IsVisible() && iSearchField->IsFocused())
    		{
    		((MEikCommandObserver*)&*iSearchField)->ProcessCommandL(EAknSoftkeyCancel);
    		}
    	}
    
    void ShowSearchFieldL()
    {
		if(iBreakpoint.Get()) (*iBreakpoint.Get())();
		if(iSearchField->IsVisible()) return;
    	iSearchField->MakeVisible(ETrue);
    	
		TVwsViewId viewId;
		iAvkonAppUi->GetActiveViewId(viewId);
		if( viewId.iViewUid.iUid == iViewId)
			{
			iSearchField->SetFocus(ETrue);
			// if we SetFocus(ETrue) while the view is not active, HandleViewDeactivation will not be called, and SetFocus(EFalse) will not be called, which generates panic EIKCOTL 63
			
			//AknFind::HandlePopupFindSizeChanged(this, iListBox.Get(), iSearchField.Get());
			// technique from https://developer.symbian.org/oss/FCL/sf/app/musicplayer/raw-annotate/b70d77332e66/mpxplugins/viewplugins/views/addtracksdialog/src/mpxaddtracksdialog.cpp
			// in order to be notified of Cancel button presses
			{
				CEikButtonGroupContainer* cba = CEikButtonGroupContainer::Current();
				cba->RemoveCommandObserver(2);
				cba->UpdateCommandObserverL(2,*this);
			}
			}
    	SizeChanged();
    }
    
    virtual void Reset() = 0;
    

    void ProcessCommandL(TInt aCommandId)
    	{
		if(iBreakpoint.Get()) (*iBreakpoint.Get())();
    	if(iSearchField->IsVisible() && iSearchField->IsFocused())
    		{
    		((MEikCommandObserver*)&*iSearchField)->ProcessCommandL(aCommandId);
    		}
    	if(aCommandId == EAknSoftkeyCancel) // Cancel clicked on searchField
    		{
    		HandleControlEventL(&*iSearchField, MCoeControlObserver::EEventRequestCancel);
    		SizeChanged();
    		iListBox->DrawDeferred();
    		}
    	}

/*    TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType)
    	{
    	iLastScanCode = aKeyEvent.iScanCode;
    	
		if(aType == EEventKeyDown)
			switch(aKeyEvent.iScanCode)
				{
				case EStdKeyLeftArrow:
					{
					if(iListBox->CurrentItemIndex() > 0)
						{
						iListBox->SetCurrentItemIndex(0);
						iListBox->ScrollToMakeItemVisible(0);
						iListBox->DrawDeferred();
						return EKeyWasConsumed;
						}
					}
					break;
				case EStdKeyRightArrow:
				case EStdKeyUpArrow:
				case EStdKeyDownArrow:
				case EStdKeyDevice0: // left soft key
				case EStdKeyDevice1: // right soft key
				case EStdKeyRightShift: case EStdKeyLeftShift: case EStdKeyHash: // Pen keys
				case EStdKeyDevice3: // ScanCode for EKeyOK
				break;
				default:
					{
					if(!iSearchField->IsVisible())
						{
						ShowSearchFieldL();
						return EKeyWasConsumed;
						}
					}
				}
		
		
		if(aType == EEventKey)
			switch(aKeyEvent.iCode)
				{
				case EKeyLeftArrow:
				case EKeyRightArrow:
					break;
				case EKeyUpArrow:
				case EKeyDownArrow:
				case EKeyOK:
					if(!iListBox->IsFocused())
						iListBox->SetFocus(ETrue);
					return iListBox->OfferKeyEventL(aKeyEvent, aType);
				default:
					if(!iSearchField->IsVisible())
						ShowSearchFieldL();
					return iSearchField->OfferKeyEventL(aKeyEvent, aType);
				}
        
        return EKeyWasNotConsumed;
    	}

            
	};*/

        TKeyResponse OfferKeyEventL(const TKeyEvent& aKeyEvent,TEventCode aType)
        	{
        	TFunTrace funTrace(__PRETTY_FUNCTION__);
        	iLastScanCode = aKeyEvent.iScanCode;
        	
        	
    		if(aType == EEventKeyDown)
    			{
    			// this section is ONLY to show the search field when a key is pressed.
    			// EEventKey does not work for that purpose (the key is not propagated correctly)
    			switch(aKeyEvent.iScanCode)
    				{
    				case EStdKeyLeftArrow:
    				case EStdKeyRightArrow:
    				case EStdKeyUpArrow:
    				case EStdKeyDownArrow:
    				case EStdKeyDevice0: // left soft key
    				case EStdKeyDevice1: // right soft key
    				case EStdKeyRightShift: case EStdKeyLeftShift: case EStdKeyHash: // Pen keys
    				case EStdKeyDevice3: // ScanCode for EKeyOK
    				break;
    				default:
    					{
    					if(!iSearchField->IsVisible() && iSearchTextChangedFun)
    						{
    						ShowSearchFieldL();
    						return EKeyWasConsumed;
    						}
    					}
    				}
    			}
    		        	
        	
			switch(aKeyEvent.iCode)
				{
				case EKeyLeftArrow:
					if(iListBox->CurrentItemIndex() > 0)
						{
						iListBox->SetCurrentItemIndex(0);
						iListBox->ScrollToMakeItemVisible(0);
						iListBox->DrawDeferred();
						return EKeyWasConsumed;
						}
					break;
				case EKeyUpArrow:
				case EKeyDownArrow:
				case EKeyOK:
					if(!iListBox->IsFocused())
						iListBox->SetFocus(ETrue);
					return iListBox->OfferKeyEventL(aKeyEvent, aType);
				default:
					// http://git.zx2c4.com/qt/plain/src/gui/kernel/qapplication_s60.cpp?h=4.7&id=v4.6.0
					if(aKeyEvent.iCode > EKeyNull && aKeyEvent.iCode < ENonCharacterKeyBase && iSearchTextChangedFun)
						{
						if(!iSearchField->IsVisible())
							ShowSearchFieldL();
						return iSearchField->OfferKeyEventL(aKeyEvent, aType);
						}
				}
            
            return EKeyWasNotConsumed;
        	}

                
    	};



template<typename T>
class CIteratorControl : public CBaseIteratorControl
	{
	public:
    CIterator<T>* iUnderlying;
    mutable RArray<T> iPtrs;
    LManagedPtr< scalp::CFunction<void(TDes& aDes, T)> > iFormatFun;
    LManagedPtr< scalp::CFunction< void(T, TInt, const TRect&, TBool, TBool, TBool, TBool )> > iDrawItemFun;
    
    LManagedPtr<CAsyncCallBack> iSetCurrentItemIndexAsyncCallback;
    
    static CArrayIterator<T> EmptyUnderlying;
    
    CIteratorControl() : iUnderlying( &EmptyUnderlying )
	{
	
	}
    
	TInt NumberOfItems() const
	{
	if(iUnderlying->HasNext())
		{
		return KMaxTInt16; // ideally we would like KMaxTInt here but S60 5th edition devices have a problem with KMaxTInt value (1: Scrolling down scrolls back, and 2: panic "EIKON-LISTBOX 4" on OfferKerEventL with EKeyUpArrow) 
		return KMaxTInt;
		}
	return iPtrs.Count();
	}

	void DrawActualItem(TInt aIndex, const TRect& aActualItemRect,
			TBool aItemIsCurrent, TBool aViewIsEmphasized, TBool aViewIsDimmed, 
			TBool aItemIsSelected)
			{
			T t;
			if( GetItemL(aIndex, t) )  	
				{
				(*iDrawItemFun)(iPtrs[aIndex], aIndex, aActualItemRect, aItemIsCurrent, aViewIsEmphasized, aViewIsDimmed, aItemIsSelected); // notification
				}
			}

	const MDesCArray* MatchableTextArray() const
	{
		return NULL;
	}
    
    
    TInt SetCurrentItemIndexCallbackL()
    	{
    	TFunTrace funTrace(__PRETTY_FUNCTION__);
    	if(iPtrs.Count() && iListBox->CurrentItemIndex() == KErrNotFound)
    		{
    		switch(iLastScanCode)
    			{
    			case EStdKeyUpArrow:
    				iListBox->SetTopItemIndex(0);
    				iListBox->SetCurrentItemIndex(iPtrs.Count() - 1);
    				break;
    			default:
    				iListBox->SetCurrentItemIndex(0);
    				break;
    			}
    		iListBox->ScrollToMakeItemVisible(iListBox->CurrentItemIndex());
    		}
    	iListBox->DrawDeferred();
    	return EFalse; // run only once
    	}
    
    

    void ConstructL(const TRect& aRect, CEikListBox* aListBox, CCoeControl* aParent = NULL, TInt aViewId = KErrNone)
    	{
    	CBaseIteratorControl::ConstructL(aRect, aListBox, aParent, aViewId);
    	iSetCurrentItemIndexAsyncCallback = new (ELeave) CAsyncCallBack( TCallBack(&MemFunDelegate<CIteratorControl, &CIteratorControl::SetCurrentItemIndexCallbackL>, this), CActive::EPriorityStandard);
    	}
    
    void SetUnderlying(CIterator<T>* aUnderlying)
    	{
    	iUnderlying = aUnderlying;
    	Reset();
    	}
    
    void Reset()
    	{
    	iPtrs.Reset();
    	iUnderlying->Reset();
    	iListBox->HandleItemRemovalL();
    	iListBox->HandleItemAdditionL();
    	}
    
    TInt MdcaCount() const
    {	
    	if(iUnderlying->HasNext())
    		{
    		return KMaxTInt;
    		}
    	return iPtrs.Count();
    }
    
    TBool GetItemL(TInt aIndex, T& aT) const
    	{
    	if(aIndex < iPtrs.Count())
    		aT = iPtrs[aIndex];
    	TBool underlyingHasNext = iUnderlying->HasNext();
    	while(aIndex >= iPtrs.Count() && underlyingHasNext )
    		{
    		if(iUnderlying->Next(aT))
    			iPtrs.AppendL( aT );
    		underlyingHasNext = iUnderlying->HasNext();
    		if(!underlyingHasNext)
    			{
        		iListBox->HandleItemRemovalL();
        		DrawDeferred();	
        		iSetCurrentItemIndexAsyncCallback->Cancel();
        		iSetCurrentItemIndexAsyncCallback->CallBack();
    			}
    		}
    	return(aIndex < iPtrs.Count());
    	}
    
    
    LManagedPtr< scalp::CFunction<void()> > iMdcaPointBreakpoint;

    TPtrC16 MdcaPoint(TInt aIndex) const
    {
    
		if(iMdcaPointBreakpoint.Get()) (*iMdcaPointBreakpoint)();
    
		T t;
		if( GetItemL(aIndex, t) )  	
			{
			(*iFormatFun)(iArrayItem, iPtrs[aIndex]); // notification
    		return iArrayItem;
			}
		else
    		return KNullDesC();
    }

    
    ~CIteratorControl()
    	{
    	if(iSearchField.Get() && iSearchField->IsFocused() && iSearchField->IsVisible())
    		{
    		iSearchField->SetFocus(EFalse);
    		}
    	iAvkonViewAppUi->RemoveViewDeactivationObserver(this);
    	iPtrs.Close();
    	}
    	
	};

template<typename T>
CArrayIterator<T> CIteratorControl<T>::EmptyUnderlying(NULL, 0);


#endif // __ITERATOR_CONTROL__
